﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Timers;

namespace Chat
{
    class Server
    {

        Socket mainSocket;
        List<Client> clientList;
        AsyncCallback func;
        IAsyncResult aResult;

        byte[] recBuffer;
        const int MAX_BUFFER_SIZE = 64;


        public event EventHandler ServerMessage = delegate { };


        public Server()
        {
            clientList = new List<Client>();
            mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //buffer = new byte[MAX_BUFFER_SIZE];
            func = new AsyncCallback(ConnectionAccepted);


        }

        public void StartServer(int port)
        {
            EndPoint endPoint = new IPEndPoint(IPAddress.Any, port);
            mainSocket.Bind(endPoint);
            mainSocket.Listen(5);
            aResult = mainSocket.BeginAccept(func, null);

            //Console.Out.WriteLine("Server: Listening on port " + port);
        }


        private void ConnectionAccepted(IAsyncResult aResult)
        {

            Socket newSock = mainSocket.EndAccept(aResult);
            Client newClient = new Client();
            newClient.Socket = newSock;
            clientList.Add(newClient);
            newClient.Socket.BeginReceive(newClient.Buffer, 0, newClient.Buffer.Length, SocketFlags.None, MessageReceived, newClient);

            //display in the server panel that someone has connected
            DisplayServerMessage("Connection Accepted from IP " + ((IPEndPoint)(newSock.RemoteEndPoint)).Address.ToString());
            //give the new client 3 seconds to identify themselves
            newClient.IdentifyTimer.Interval = 3;
            newClient.IdentifyTimer.AutoReset = false;
            newClient.IdentifyTimer.Elapsed += new ElapsedEventHandler(IdentifyTimer_Elapsed);

            aResult = mainSocket.BeginAccept(func, null);
        }

        void IdentifyTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            //if the new user is still unidentified
            //close the socket
        }




        void MessageReceived(IAsyncResult asyn)
        {
            // Retrieve the socket from the state object.
            Client client = (Client)asyn.AsyncState;

            int bytesReceived = client.Socket.EndReceive(asyn);
            if (bytesReceived > 0)
            {
                client.ReceivedData.AddRange(client.Buffer);

                //if there are still bytes available to be read
                //then we havent received all the bytes yet
                if (client.Socket.Available == 0)
                {

                    Message receivedMsg = Message.Deserialize(client.ReceivedData.ToArray(), client.ReceivedData.ToArray().Length);
                    //process the received message
                    ProcessMessage(receivedMsg, client);
                    client.ReceivedData.Clear();
                }
                client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, SocketFlags.None, MessageReceived, client);

            }
            else
            {
                //ServerMessage(this, new ChatEventArgs(client.ClientName+" has disconnected"));

                RelayMessage(new ChatMessage(client.ClientName + " has disconnected"));
                IPEndPoint endPoint = (IPEndPoint)client.Socket.RemoteEndPoint;
                endPoint.Address.ToString();
                DisplayServerMessage(endPoint.Address.ToString()+":"+client.ClientName + " has disconnected");
                client.Socket.Shutdown(SocketShutdown.Both);
                client.Socket.Close();
                clientList.Remove(client);
            }
        }

        private void ProcessMessage(Message receivedMsg, Client client)
        {
            if (receivedMsg.GetType() == typeof(ChatMessage))
            {
                //just relay the message to everyone
                //with the name of the sender prepended
                ChatMessage msg = (ChatMessage)receivedMsg;
                msg.Text = client.ClientName + ": " + msg.Text;
                RelayMessage(msg);
            }
            else if (receivedMsg.GetType() == typeof(CommandMessage))
            {
                CommandMessage cmd = (CommandMessage)receivedMsg;
                switch (cmd.Command)
                {
                    case CommandMessage.CommandType.Identify:
                        client.ClientName = cmd.Args;
                        RelayMessage(new ChatMessage(client.ClientName + " has connected"));

                        IPEndPoint endPoint = (IPEndPoint)client.Socket.RemoteEndPoint;
                        endPoint.Address.ToString();

                        DisplayServerMessage(endPoint.Address.ToString() + " has been Identified as " + client.ClientName);

                        break;

                    default:
                        throw new Exception("Unknown Command Type!");
                        break;
                }

                //echo the command received to the server panel
                //receivedMsg.Data = "Data received from client: " + receivedMsg.Data;
                //ServerMessage(this, new ChatEventArgs(receivedMsg));

            }




        }


        public void Dispose()
        {
            if (mainSocket.Connected)
            {
                mainSocket.Shutdown(SocketShutdown.Both);
                mainSocket.EndAccept(aResult);

                mainSocket.Disconnect(false);
            }

            foreach (Client tempClient in clientList)
            {
                tempClient.Socket.Disconnect(false);
            }
            mainSocket.Close();
            Console.Out.WriteLine("Server: Stopped!");
        }

        private void DisplayServerMessage(string textIn)
        {
            ChatEventArgs args = new ChatEventArgs(new ChatMessage(textIn));
            ServerMessage(this, args);

        }

        private void RelayMessage(Message msg)
        {
            foreach (Client client in clientList)
            {

                client.Socket.Send(msg.Serialize());

            }
        }


    }
}